import { RenderingManager } from '@babylonjs/core/Rendering/renderingManager';
import { Helper } from '../tool/helper';
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';


/**
 * 渲染顺序组对象
 * @param name 名称
 * @param id 顺序值
 */
class Order {
    public readonly name: string;
    private _id: number;
    private _targets: TransformNode[] = [];
    constructor(name: string, id: number) {
        this.name = name;
        this._id = id;
    }

    /**
     * 获取顺序值
     */
    public get id(): number {
        return this._id;
    }

    /**
     * 添加目标对象
     * @param targets 要设置到当前渲染顺序的对象
     */
    public add(targets: TransformNode[]): void {
        Order.setGroupID(targets, this.id);
        this._targets.push(...targets);
    }

    /**
     * 更新设置顺序值
     * @param id 顺序值
     */
    public update(id: number): void {
        this._id = id;
        Order.setGroupID(this._targets, id);
    }

    /**
     * 设置目标对象的顺序值
     * @param targets 目标对象
     * @param renderGroupID 顺序值
     */
    private static setGroupID(targets: TransformNode[], renderGroupID: number): void {
        targets.forEach(target => {
            const ms = Helper.getChildMeshes(target);
            for (const m of ms) {
                m.renderingGroupId = renderGroupID;
            }
        });
    }
}


/**
 * 渲染顺序管理（使用initialize初始化后使用）
 */
class RenderOrder {

    private static _orders: Order[] = [];

    /**
     * 最先渲染的默认顺序组名称
     */
    public static readonly baseName = 'base';

    /**
     * 最后渲染的默认顺序组名称
     */
    public static readonly topName = 'top';

    /**
     * 初始化顺序组
     * @param lowToHightOrderedNames 顺序值从低到高（先渲染到后渲染）排列的顺序组名称集合
     */
    public static initialize(...lowToHightOrderedNames: string[]): void {
        RenderingManager.MAX_RENDERINGGROUPS = lowToHightOrderedNames.length + 5;
        this._orders.push(new Order(this.baseName, 0));
        let c = 1;
        for (const name of lowToHightOrderedNames) {
            this._orders.push(new Order(name, c));
            c++;
        }
        this._orders.push(new Order(this.topName, c));
    }

    /**
     * 获取顺序组的顺序值
     * @param orderName 顺序组名称
     * @returns 顺序值
     */
    public static id(orderName: string): number {
        return this.getOrder(orderName).id;
    }

    /**
     * 获取对象或顺序值所对应的顺序组名称
     * @param target 顺序值或对象
     * @returns 顺序组名称
     */
    public static orderName(target: number | TransformNode): string {
        const id = (typeof target === 'number') ? target : this.getGroupID(target);
        for (const order of this._orders) {
            if (id === order.id) return order.name;
        }
        return this._orders[0].name;
    }

    /**
     * 设置对象到最先渲染的顺序组
     * @param targets 目标对象
     */
    public static base(...targets: TransformNode[]): void {
        this._orders[0].add(targets);
    }

    /**
     * 设置对象到最后渲染的顺序组
     * @param targets 目标对象
     */
    public static top(...targets: TransformNode[]): void {
        this._orders[this._orders.length - 1].add(targets);
    }

    /**
     * 设置对象到参考对象或顺序组名称所在顺序组
     * @param ref 参考对象或顺序组名称
     * @param targets 目标对象
     */
    public static equal(ref: TransformNode | string, ...targets: TransformNode[]): void {
        const odr = typeof ref === 'string' ? ref : this.getGroupID(ref);
        const order = this.getOrder(odr);
        order.add(targets);
    }

    /**
     * 设置对象到参考对象或顺序组名称所在顺序组之后的顺序组
     * @param ref 参考对象或顺序组名称
     * @param targets 目标对象
     */
    public static after(ref: TransformNode | string, ...targets: TransformNode[]): void {
        const odr = typeof ref === 'string' ? ref : this.getGroupID(ref);
        const order = this.getOrder(odr);
        if (order.name === this.topName) {
            order.add(targets);
        } else {
            this.getOrder(order.id + 1).add(targets);
        }
    }

    /**
     * 设置对象到参考对象或顺序组名称所在顺序组之后的新的顺序组（插入新的顺序组）
     * @param ref 参考对象或顺序组名称
     * @param targets 目标对象
     */
    public static afterInsert(ref: TransformNode | string, ...targets: TransformNode[]): void {
        const odr = typeof ref === 'string' ? ref : this.getGroupID(ref);
        const order = this.getOrder(odr);
        if (order.name === this.topName) {
            order.add(targets);
        } else {
            const idx = this._orders.findIndex(o => o.name === order.name);
            this._orders.splice(idx + 1, 0, new Order('after-' + order.name, idx));
            this.update();
        }
    }

    /**
     * 设置对象到参考对象或顺序组名称所在顺序组之前的顺序组
     * @param ref 参考对象或顺序组名称
     * @param targets 目标对象
     */
    public static before(ref: TransformNode | string, ...targets: TransformNode[]): void {
        const odr = typeof ref === 'string' ? ref : this.getGroupID(ref);
        const order = this.getOrder(odr);
        if (order.name === this.baseName) {
            order.add(targets);
        } else {
            this.getOrder(order.id - 1).add(targets);
        }
    }

    /**
     * 设置对象到参考对象或顺序组名称所在顺序组之前的新的顺序组（插入新的顺序组）
     * @param ref 参考对象或顺序组名称
     * @param targets 目标对象
     */
    public static beforeInsert(ref: TransformNode | string, ...targets: TransformNode[]): void {
        const odr = typeof ref === 'string' ? ref : this.getGroupID(ref);
        const order = this.getOrder(odr);
        if (order.name === this.baseName) {
            order.add(targets);
        } else {
            const idx = this._orders.findIndex(o => o.name === order.name);
            this._orders.splice(idx, 0, new Order('before-' + order.name, idx));
            this.update();
        }
    }

    /**
     * 更新顺序组顺序值（插入新顺序组后应更新）
     */
    private static update(): void {
        for (let i = 0; i < this._orders.length; i++) {
            this._orders[i].update(i);
        }
    }

    /**
     * 根据名称或顺序值获取顺序组
     * @param order 顺序组名称或顺序值
     * @returns 顺序组
     */
    private static getOrder(order: string | number): Order {
        const isId = typeof order === 'number';
        for (const odr of this._orders) {
            if (isId ? order === odr.id : order === odr.name) return odr;
        }
        return this._orders[0];
    }

    /**
     * 获取对象的顺序值
     * @param target 目标对象
     * @returns 顺序值
     */
    private static getGroupID(target: TransformNode): number {
        const m = Helper.getChildMeshes(target)[0];
        return m ? m.renderingGroupId : 0;
    }

}


export { RenderOrder };